package idv.takeshi.software.productline.bookstore.domain.model.order;


import idv.takeshi.software.productline.bookstore.domain.model.deliveryservice.DeliveryService;

import java.util.Date;
import java.util.Set;

import org.apache.commons.lang.Validate;

/**
 * A state pattern for Order's status.
 * @author takeshi
 *
 */
public enum OrderStatus {
	
	/**
	 * A New status for order.
	 * @author takeshi
	 *
	 */
	NEW(){
		
		/* (non-Javadoc)
		 * @see idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus#checkOrderItemsStatus(idv.takeshi.software.productline.bookstore.domain.model.order.Order)
		 */
		@Override
		public void checkOrderItemsStatus(Order thisOrder) {
			Validate.notNull(thisOrder, "thisOrder shall always not be null");
			Set<OrderItem> orderItems = thisOrder.getOrderItems();
			boolean hasWaitStatus = false;
			if(orderItems.size() > 0){
				for(OrderItem orderItem : orderItems){
					orderItem.checkReplenishmentNeeded();
					if(orderItem.getStatus() == OrderItemStatus.WAIT){
						hasWaitStatus = true;
					}
				}
				if(hasWaitStatus){
					thisOrder.setStatus(WAIT_FOR_REPLENISHMENT);
					thisOrder.setUpdateTime(new Date());
				}
			}
		}
		
		/* (non-Javadoc)
		 * @see idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus#process(idv.takeshi.software.productline.bookstore.domain.model.order.Order)
		 */
		@Override
		public void process(Order thisOrder) throws OrderException {
			Validate.notNull(thisOrder, "thisOrder shall always not be null");
			Payment payment = thisOrder.getPayment();
			boolean underProcess = false;
			Validate.notNull(payment, "payment shall always not be null");
			if(payment instanceof BeforeDeliveryPayment){
				if(payment.isPaid()){
					thisOrder.setStatus(UNDER_PROCESSING);
					thisOrder.setUpdateTime(new Date());
					underProcess = true;
				} else{
					throw new OrderStatusToUnderProcessingException("payment:" + payment +
							" not paid yet");
				}
			} else{
				thisOrder.setStatus(UNDER_PROCESSING);
				thisOrder.setUpdateTime(new Date());
				underProcess = true;
			}
			
			if(underProcess){
				thisOrder.addPickingList();
			}
		}
		
	},
	
	/**
	 * A WaitForReplenishment status for order.
	 * @author takeshi
	 *
	 */
	WAIT_FOR_REPLENISHMENT(){
		/* (non-Javadoc)
		 * @see idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus#process(idv.takeshi.software.productline.bookstore.domain.model.order.Order)
		 */
		@Override
		public void process(Order thisOrder) throws OrderException {
			Validate.notNull(thisOrder, "thisOrder shall always not be null");
			Set<OrderItem> orderItems = thisOrder.getOrderItems();
			
			if(orderItems.size() > 0){
				boolean anyWaitStatus = false;
				for(OrderItem orderItem : orderItems){
					orderItem.checkReplenishmentCompleted();
					if(orderItem.getStatus() == OrderItemStatus.WAIT){
						anyWaitStatus = true;
					}
				}
				if(!anyWaitStatus){
					thisOrder.setStatus(NEW);
					thisOrder.setUpdateTime(new Date());
				}
			}
		}
		
	},
	
	/**
	 * A UnderProcessing status for order.
	 * @author takeshi
	 *
	 */
	UNDER_PROCESSING(){
		/* (non-Javadoc)
		 * @see idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus#cancel(idv.takeshi.software.productline.bookstore.domain.model.order.Order)
		 */
		@Override
		public void cancel(Order thisOrder) {
			super.cancel(thisOrder);
			PickingList pickingList = thisOrder.getActivePickingList(); 
			pickingList.setStatus(PickingListStatus.CANCEL);
			pickingList.setUpdateTime(new Date());
		}

		/* (non-Javadoc)
		 * @see idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus#deliver(idv.takeshi.software.productline.bookstore.domain.model.order.Order)
		 */
		@Override
		public void deliver(Order thisOrder) {
			Validate.notNull(thisOrder, "thisOrder shall always not be null");
			Set<OrderItem> orderItems = thisOrder.getOrderItems();
			for(OrderItem orderItem : orderItems){
				orderItem.deliver();
			}
			Date dateTime = new Date();
			PickingList pickingList = null;
			thisOrder.setStatus(UNDER_DELIVERY);
			thisOrder.setUpdateTime(dateTime);
			pickingList = thisOrder.getActivePickingList(); 
			pickingList.setStatus(PickingListStatus.UNDER_DELIVERY);
			pickingList.setUpdateTime(dateTime);
		}
		
	},
	
	/**
	 * A UnderDelivery status for order.
	 * @author takeshi
	 *
	 */
	UNDER_DELIVERY(){
		/* (non-Javadoc)
		 * @see idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus#arrive(idv.takeshi.software.productline.bookstore.domain.model.order.Order)
		 */
		@Override
		public void arrive(Order thisOrder) {
			Validate.notNull(thisOrder, "thisOrder shall always not be null");
			DeliveryService deliveryService = thisOrder.getDeliveryService();
			Validate.notNull(deliveryService, "deliveryService shall always not be null");
			
			Date dateTime = new Date();
			PickingList pickingList = null;
			if(deliveryService.isPostal()){
				thisOrder.setStatus(RECEIVED);
				thisOrder.setUpdateTime(dateTime);
				pickingList = thisOrder.getActivePickingList();
				pickingList.setStatus(PickingListStatus.RECEIVED);
				pickingList.setUpdateTime(dateTime);
			} else{
				thisOrder.setStatus(IN_CONVENIENT_STORE);
				thisOrder.setUpdateTime(dateTime);
				pickingList = thisOrder.getActivePickingList(); 
				pickingList.setStatus(PickingListStatus.IN_CONVENIENT_STORE);
				pickingList.setUpdateTime(dateTime);
			}
		}

		
		/**
		 * Cancel is invalid operation for this status. 
		 */
		@Override
		public void cancel(Order thisOrder) {
			throw new UnsupportedOperationException(INVALID_OPERATION);
		}
	},
	
	/**
	 * A Received status for order.
	 * @author takeshi
	 *
	 */
	RECEIVED(){
		/**
		 * Cancel is invalid operation for this status. 
		 */
		@Override
		public void cancel(Order thisOrder) {
			throw new UnsupportedOperationException(INVALID_OPERATION);
		}

		/* (non-Javadoc)
		 * @see idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus#close(idv.takeshi.software.productline.bookstore.domain.model.order.Order)
		 */
		@Override
		public void close(Order thisOrder) {
			Validate.notNull(thisOrder, "thisOrder shall always not be null");
			Date dateTime = new Date();
			PickingList pickingList = null;
			thisOrder.setStatus(CLOSE);
			thisOrder.setUpdateTime(dateTime);
			pickingList = thisOrder.getActivePickingList(); 
			pickingList.setStatus(PickingListStatus.CLOSE);
			pickingList.setUpdateTime(dateTime);
		}
		
	},
	
	/**
	 * A InConvenientStore status for order.
	 * @author takeshi
	 *
	 */
	IN_CONVENIENT_STORE(){
		/**
		 * Cancel is invalid operation for this status. 
		 */
		@Override
		public void cancel(Order thisOrder) {
			throw new UnsupportedOperationException(INVALID_OPERATION);
		}

		/* (non-Javadoc)
		 * @see idv.takeshi.software.productline.bookstore.domain.model.order.OrderStatus#close(idv.takeshi.software.productline.bookstore.domain.model.order.Order)
		 */
		@Override
		public void close(Order thisOrder) {
			Validate.notNull(thisOrder, "thisOrder shall always not be null");
			Date dateTime = new Date();
			PickingList pickingList = null;
			thisOrder.setStatus(CLOSE);
			thisOrder.setUpdateTime(dateTime);
			pickingList = thisOrder.getActivePickingList(); 
			pickingList.setStatus(PickingListStatus.CLOSE);
			pickingList.setUpdateTime(dateTime);
		}
		
	},
	
	/**
	 * A Close status for order.
	 * @author takeshi
	 *
	 */
	CLOSE(){
		/**
		 * Cancel is invalid operation for this status. 
		 */
		@Override
		public void cancel(Order thisOrder) {
			throw new UnsupportedOperationException(INVALID_OPERATION);
		}
		
	},
	
	/**
	 * A Cancel status for order.
	 * @author takeshi
	 *
	 */
	CANCEL(){
		/**
		 * Cancel is invalid operation for this status. 
		 */
		@Override
		public void cancel(Order thisOrder) {
			throw new UnsupportedOperationException(INVALID_OPERATION);
		}
	};
	
	
 
	private static final String INVALID_OPERATION = "Invalid Operation";


	/**
	 * Check order's orderitems are whether in correct status.
	 * @param thisOrder
	 */
	public void checkOrderItemsStatus(Order thisOrder) {
		throw new UnsupportedOperationException(INVALID_OPERATION);
	}
	 
	/**
	 * Change the order's status to under-process, if all check rules are pass.
	 * @param thisOrder
	 * @throws OrderException 
	 */
	public void process(Order thisOrder) throws OrderException {
		throw new UnsupportedOperationException(INVALID_OPERATION);
	}
	
	/**
	 * Change the order's status to under-delivery, if all check rules are pass.
	 * @param thisOrder
	 */
	public void deliver(Order thisOrder) {
		throw new UnsupportedOperationException(INVALID_OPERATION);
	}
	 
	/**
	 * Change the order's status to arrived, if all check rules are pass.
	 * @param thisOrder
	 */
	public void arrive(Order thisOrder) {
		throw new UnsupportedOperationException(INVALID_OPERATION);
	}
	 
	/**
	 * Change the order's status to close, if all check rules are pass.
	 * @param thisOrder
	 */
	public void close(Order thisOrder) {
		throw new UnsupportedOperationException(INVALID_OPERATION);
	}

	
	/**
	 * Change the order's status to cancel, if all check rules are pass.
	 * @param thisOrder
	 */
	public void cancel(Order thisOrder) {
		Validate.notNull(thisOrder, "thisOrder shall always not be null");
		Set<OrderItem> orderItems = thisOrder.getOrderItems();
		for(OrderItem orderItem : orderItems){
			orderItem.cancel();
		}
		thisOrder.setStatus(CANCEL);
		thisOrder.setUpdateTime(new Date());
	}
	 
}
 
